/*
TP 0:

Versión 0.8.0 13/09/2013    - Diego Luna


Authors:

Gonzalo Prieto  <gonprieto_AT_gmail_DOT_com>
Ramiro Doi      <ramirodoi_AT_gmail_DOT_com>
Diego Luna      <diegorluna_AT_gmail_DOT_com>

*/


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <getopt.h>
#include <ctype.h>
#include <errno.h>

#include "globals.h"
#include "exiterror.h"
#include "image_writer.h"
#include "tp0.h"




/*/////////////////////// Constantes del main - BEGIN - /////////////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

#ifndef VERSION
#define VERSION "0.8.0"
#endif

#define STR_VERSION "TP0 66.20 version: %s\n"



/*
Command line options

-V, --version Print version and quit.
-h, --help Print this information.
-r, --resolution Set bitmap resolution to WxH pixels.
-o, --output Path to output file.

*/
#define USAGE_TEXT		"\nUsage:\n\n"\
				"\t%s -V\n"\
				"\t%s -h\n"\
				"\t%s [options]\n\n"\
				"Options:\n\n"


#define HELP_TEXT		"\t-V, --version\t\tPrint version and quit.\n"\
				"\t-h, --help\t\tPrint this information.\n"\
				"\t-r, --resolution\tSet bitmap resolution to WxH pixels.\n\n"\
				"\t-o, --output\t\tPath to output file.\n"


#define EXAMP_TEXT		"Examples:\n\n"\
				"\t%s -o output.pgm\n"\
				"\t%s -r 800x600 -o file.pgm\n\n"


#define FILE_REDIR_STD          "-"
#define FILE_STD_OUT_NAME       "<Standard output>"
#define RESOLUTION_SEPARATOR    'x'


/*///////////////////////////////////////////////////////////////////////////////////////*/
/*///////////////////////// Constantes del main - END - /////////////////////////////////*/


/*/////////////////// Variables globales que se exportan - BEGIN - //////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

const char* pcoutputfile = NULL;


/*/////////////////// Variables globales que se exportan - END - ////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/


/*/////////////////////// Variables globales del main - BEGIN - /////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

static bool bspecoutput_opt = false;
static bool bresolution_opt = false;
static bool bout_is_stdout = false;

static t_image_size image_size = {8, 8};

/*///////////////////////////////////////////////////////////////////////////////////////*/
/*/////////////////////// Variables globales del main - END - ///////////////////////////*/


/*/////////////////////////////// Funciones del main - BEGIN - //////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/

static void clean_exit(const int, const bool);

static void parse_cmdline(int, char * const []);

static void do_usage(const char*, const int);
static void do_version(const char*);
static void do_output(const char*, const char*);
static void do_resolution(const char*, const char*);


bool parse_image_size(t_image_size *image_size_ptr, const char *str);

/*/////////////////////////////// Funciones del main - END - ////////////////////////////*/
/*///////////////////////////////////////////////////////////////////////////////////////*/


/*
Mestra por stderr un mensaje de status (error) y termina el programa devolviendo
un valor correspondiente al status de salida.

1er parámetro: Status de salida.

2do parámetro: Indica si se debe o no mostrar el texto del error.

devuelve: -.
*/
static void clean_exit(const int istatus, const bool bshowerrormessage)
{
    if (bshowerrormessage)
        messageerror(istatus);	    				/* Convierto el error a texto y lo muestro. */

    if (pcoutputfile && !bout_is_stdout && (istatus != ERROR_SUCCESS))
        remove(pcoutputfile);   				/* Borro el archivo de salida en caso de error. */

    exit(istatus);						/* Termino el programa devolviendo el código de error. */
}


/*
Procesa la línea de comandos.

1er parámetro: Cantidad de argumentos del programa.

2do parámetro: Puntero a los argumentos.


devuelve: -. (La función puede terminar el programa)
*/
static void parse_cmdline(int argc, char * const argv[])
{
    int ch;							/* Valor devuelto por la función getopt_long */
    int opt_index = 0;						/* Valor devuelto por la función getopt_long */

    struct option optionarr[5] =
    {
        {"version", no_argument, NULL, 'V'},			/* Versión del programa */
        {"help", no_argument, NULL, 'h'},			/* Ayuda (uso del program) */
        {"output", required_argument, NULL, 'o'},		/* Archivo de salida */
        {"resolution", required_argument, NULL, 'r'},		/* Resolución de la imagen */
        {NULL, no_argument, NULL, 0}
    };

    /*
    Llama repetidamente a la función getopt (devuelve el caracter que
    representa a la opción o EOF) hasta procesar todos
    los argumentos
    */
    opterr = 0;							/* No quiero que getopt_long imprima errores */
    opt_index = 0;						/* Inicializo opt_index (indice de opcion)*/

    while ((ch = getopt_long(argc, argv, ":Vho:r:",
                             optionarr, &opt_index)) != -1)
    {
        switch (ch)
        {
        case 'V':
            do_version(argv[0]);
            break;
        case 'h':
            do_usage(argv[0], ERROR_SUCCESS);
            break;
        case 'o':
            do_output(argv[0], optarg);
            break;
        case 'r':
            do_resolution(argv[0], optarg);
            break;
        case '?':
            do_usage(argv[0], ERROR_INVALID_COMMAND_LINE);
        case ':':
            do_usage(argv[0], ERROR_MISSING_REQUIRED_PARAMETER);

        default:
            do_usage(argv[0], ERROR_INVALID_COMMAND_LINE);
        }
    }


    /* Proceso posibles parámetros extra (no debería haberlos). */
    if (optind < argc)
        clean_exit(ERROR_INVALID_COMMAND_LINE, true);

    /*
    Default resolution "8x8".
    */
    if (!bresolution_opt)
        messageinfo(INFO_STR_DEFAULTING_RESOLUTION);

    /*
    Default output "stdout".
    */
    if (!bspecoutput_opt)
        messageinfo(INFO_STR_DEFAULTING_OUTPUT_TO_STDOUT);
}


/*
Imprime la ayuda de la linea de comandos
para el programa.

1er parámetro: Puntero al nombre del ejecutable.

2do parámetro: Puntero al parámetro de la opcion.


devuelve: -.
*/
static void do_usage(const char *pcexename, const int istatus)
{
    if (istatus != ERROR_SUCCESS)				/* Si no se llamo por invocar con -h o --help */
        messageerror(istatus);

    fprintf(stderr, USAGE_TEXT, pcexename, pcexename, pcexename);

    fprintf(stderr, HELP_TEXT);

    fprintf(stderr, EXAMP_TEXT, pcexename, pcexename);

    clean_exit(istatus, false);
}


/*
Imprime la version del programa.

1er parámetro: Puntero al nombre del ejecutable.


devuelve: -.
*/
static void do_version(const char *pcexename)
{
    fprintf(stderr, STR_VERSION, VERSION);
    clean_exit(ERROR_SUCCESS, false);
}


/*
Determina el archivo de salida.

1er parámetro: Puntero al nombre del ejecutable.

2do parámetro: Puntero al parámetro de la opcion.


devuelve: -.
*/
static void do_output(const char *pcexename, const char *pcargs)
{
    if (!pcargs || !strlen(pcargs))				/* Valido argumentos. */
        clean_exit(ERROR_NEED_ARGUMENTS, true);

    if(bspecoutput_opt)						/* Opción repetida. */
        clean_exit(ERROR_ONLY_ONE_OUTPUT, true);

    pcoutputfile = pcargs;					/* Guardo el nuevo archivo de salida. */

    bspecoutput_opt = true;
}


/*
Determina la resolución de la imagen a mostrar.

1er parámetro: Puntero al nombre del ejecutable.

2do parámetro: Puntero al parámetro de la opcion.


devuelve: -.
*/
static void do_resolution(const char *pcexename, const char *pcargs)
{
    if (!pcargs || !strlen(pcargs))				/* Valido argumentos. */
        clean_exit(ERROR_NEED_ARGUMENTS, true);

    if(bresolution_opt)						/* Opción repetida. */
        clean_exit(ERROR_ONLY_ONE_RESOLUTION, true);

    if (!parse_image_size(&image_size, pcargs))			/* Parseo la resolución. */
        clean_exit(ERROR_INVALID_RESOLUTION, true);

    bresolution_opt = true;
}


/*
Parsea el string que reprenta el tamaño de la imagen.

1er parámetro: Puntero a la estructura del tamaño.

2do parámetro: String a parsear.


devuelve: Éxito al parsear.
*/
bool parse_image_size(t_image_size *image_size_ptr, const char *str)
{
    const char *p;
    long int width = 0, height = 0;

    /* Valido el string de entrada. */
    if (!str || !strlen(str))
        return false;

    /* convierto el ancho. */
    width = strtol(str, (void*)&p, 10);

    /* Valido el separador. */
    if (*p)
    {
        if (*p != RESOLUTION_SEPARATOR)
            return false;

        p++;
    }

    /* convierto el alto. */
    height = strtol(p, (void*)&p, 10);

    /* Detección de caracteres espúreos al final del string. */
    if (*p)
        return false;

    if (width <= 0 || height <= 0)
        return false;

    /* Verifico que sean dimensiones divisibles por BOARD_SQUARES_COUNT. */
    if (((width % BOARD_SQUARES_COUNT) != 0) || ((height % BOARD_SQUARES_COUNT) != 0))
        return false;

    image_size_ptr->width  = width;
    image_size_ptr->height = height;

    return true;
}


/*
Rutina principal del programa

1er parámetro: cantidad de argumentos.

2do parámetro: Puntero a los argumentos.

3er parámetro: Puntero a variables de entorno.


devuelve: código de error al sistema.
*/
int main(int argc, char * const argv[], char * const envp[])
{
    FILE* fostream;

    int ierror;

    /*
    proceso la linea de comandos (en caso de errores fatales, acá termina la ejecución).
    */
    parse_cmdline(argc, argv);


    /*
    Abro el archivo de salida sino es la salida standard.
    */
    bout_is_stdout = ((!bspecoutput_opt) || strequal(pcoutputfile, FILE_REDIR_STD));

    if (bout_is_stdout)
    {
        /* Redirijo mi salida a stdout */
        fostream = stdout;
        pcoutputfile = FILE_STD_OUT_NAME;
    }
    else
    {
        /* Guardo un puntero al nombre de la salida en proceso (para mensajes de error). */
        pprocfilename = pcoutputfile;

        /* Abro el archivo en modo texto en solo escritura */
        if (!(fostream = fopen(pcoutputfile, "w")))
            clean_exit(ERROR_CANNOT_OPEN_OUTPUT_FILE, true);
    }

    /* Genero la imagen */
    ierror = generate_pgm_board_image(&image_size, fostream);

    if (!bout_is_stdout)
        fclose(fostream);					/* Cierro el archivo. */

    if (ierror != ERROR_SUCCESS)				/* Se produjo un error?. */
        clean_exit(ierror, true);


    /* El proceso se completó sin errores */
    messageerror(ierror);

    return ierror;
}

